Imports System.Globalization
Imports System
Imports System.Data
Imports System.Collections
Imports System.Reflection
Imports System.Activator
Imports System.Type


''' -----------------------------------------------------------------------------
''' Project	 : BizBlox
''' Class	 : BizBlox.DataObject
''' 
''' -----------------------------------------------------------------------------
''' <summary>
''' DataObject is the foundation of a BizBlox architecture. Actual data classes 
''' inherit from this. Use the Builder tool to auto generate these inherited
''' classes.
''' </summary>
''' <remarks>
''' The DataObject is designed to be inherited based on a simple
''' pattern as explained in exampleclient.vb code. This object 
''' manages persistance through load and save methods.
''' Remeber: the constructor is lazy and will only hit the database when
''' explicitly requested.
''' 
''' NOTE 2.0 JVC TODO: need to review the performance of the new 'self awareness' code
''' to ensure its ok as a static - test high performance multi-thread race conditions
''' </remarks>
''' <history>
''' 	[cinj]	1/09/2004	Created
''' </history>
''' -----------------------------------------------------------------------------
Public MustInherit Class DataObject
    Inherits System.MarshalByRefObject

    'object handles
    Protected m_objDA As DataAccess

    'Data mapping management 
    Private m_uid As System.Guid
    Private m_commitUpdateContext As Boolean
    Private m_ancestorCommitUpdateContext As Boolean
    Private m_touched As Boolean = False
    'local cache of the shared LUT ancestry data
    Private m_ancestry() As AncestryElement

    Private Shared m_TypeLUT As New Hashtable

    ''' <summary>
    ''' Return the ancestry model for this class (the cached reflection structure)
    ''' </summary>
    ''' <returns>an array of ancestryelement entries. The array is ordered based on class heirarchy, starting with most primitive after the DataObject class</returns>
    ''' <remarks>
    ''' Each ancestry element has object level information and then also contains a StructureTable. 
    ''' </remarks>
    Friend Function GetAncestry() As AncestryElement()
        Return m_ancestry
    End Function

    'TODO: This enumeration should be commented regarding how it shadows in subclasses for the SQLBuilder
    Public Enum Columns
        DummyColumn
    End Enum

#Region "Consructors"
    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Create a new object which has a insert context
    ''' The 'context' is determined by the initialisation of the object
    ''' if you initialise without a GUID then the object doesn't map to 
    ''' a row and therefore will require data properties set.
    '''
    ''' Setting the ID property of a DataObject will not change load, save or delete. 
    ''' it is actually just passive READ ONLY.
    ''' </summary>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Sub New()
        m_commitUpdateContext = False
        m_ancestorCommitUpdateContext = False
        m_touched = False

        If m_objDA Is Nothing Then m_objDA = New DataAccess
        If Not m_TypeLUT.ContainsKey(Me.GetType()) Then
            SyncLock DataObject.m_TypeLUT.SyncRoot
                DataObject.m_TypeLUT.Add(Me.GetType(), CacheAncestry())
            End SyncLock
        End If
        m_ancestry = CType(DataObject.m_TypeLUT(Me.GetType()), AncestryElement())

        m_uid = System.Guid.NewGuid()
        Me.GetType().InvokeMember(Me.UIDColumnName(), BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {m_uid})
    End Sub


    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Create an object from a database lookup - has an update context
    ''' </summary>
    ''' <param name="uid"></param>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Sub New(ByVal uid As Guid)
        m_commitUpdateContext = True
        m_ancestorCommitUpdateContext = True
        m_touched = False

        If m_objDA Is Nothing Then m_objDA = New DataAccess
        If Not m_TypeLUT.ContainsKey(Me.GetType()) Then
            SyncLock DataObject.m_TypeLUT.SyncRoot
                DataObject.m_TypeLUT.Add(Me.GetType(), CacheAncestry())
            End SyncLock
        End If
        m_ancestry = CType(DataObject.m_TypeLUT(Me.GetType()), AncestryElement())

        m_uid = uid
        Me.GetType().InvokeMember(Me.UIDColumnName(), BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {m_uid})
    End Sub


#End Region

#Region "Overrides"
    Public MustOverride Function TableName() As String
#End Region

#Region "Self Awareness"

    Friend Function ExistsInThisObject(ByVal columnName As String) As Boolean
        Return m_ancestry(m_ancestry.Length - 1).StructureTable.ContainsKey(columnName)
    End Function


    Friend Function GetAncestorInstance(ByVal columnName As String) As DataObject
        For i As Integer = m_ancestry.Length - 1 To 0 Step -1
            If m_ancestry(i).StructureTable.ContainsKey(columnName) Then
                Return m_ancestry(i).Instance
            End If
        Next
        Throw New DOException("Invalid column name", m_ancestry(m_ancestry.Length - 1).Instance.TableName() & " does not contain a reference to " & columnName, New Exception())
    End Function

    ''' <summary>
    ''' Cache the structure of this object reflection in a high level of detail
    ''' </summary>
    ''' <param name="o"></param>
    ''' <param name="h"></param>
    ''' <remarks></remarks>
    Private Sub CacheStructure(ByVal o As Object, ByRef h As StructureElementTable, ByRef pk As String)
        Dim ht As New StructureElementTable
        Dim myPropertyInfo() As PropertyInfo
        Dim tmpAttr() As Object
        'Dim typeName As String = o.GetType().Name

        myPropertyInfo = o.GetType().GetProperties()

        For i As Integer = 0 To myPropertyInfo.Length - 1
            If myPropertyInfo(i).DeclaringType.Equals(o.GetType()) Then
                tmpAttr = myPropertyInfo(i).GetCustomAttributes(GetType(PersistAttribute), False)
                If tmpAttr.Length > 0 Then
                    Dim element As New StructureElement
                    element.CastType = myPropertyInfo(i).DeclaringType
                    element.PropertyName = myPropertyInfo(i).Name
                    element.TypeName = myPropertyInfo(i).PropertyType.ToString().ToLower()
                    'Datatype coerscion
                    tmpAttr = myPropertyInfo(i).GetCustomAttributes(GetType(DataTypeAttribute), False)
                    If tmpAttr.Length > 0 Then element.TargetTypeName = CType(tmpAttr(0), DataTypeAttribute).TypeName Else element.TargetTypeName = TypeMapper.MapTypeDefault(element.TypeName)

                    'Primary Key handling
                    tmpAttr = myPropertyInfo(i).GetCustomAttributes(GetType(PrimaryKeyAttribute), False)
                    element.PKColumn = IIf(tmpAttr.Length > 0, True, False)
                    If element.PKColumn Then pk = element.PropertyName

                    'Filter Processors
                    'this section will check that the types being passed in are in fact iDOPropertyFilter interfaces
                    Dim filterAttr() As PropertyFilterAttribute = myPropertyInfo(i).GetCustomAttributes(GetType(PropertyFilterAttribute), False)
                    Dim filters As New ArrayList
                    For x As Integer = 0 To filterAttr.Length - 1
                        If filterAttr(x).Filter.IsClass Then
                            If filterAttr(x).Filter.FindInterfaces(AddressOf GenericInterfaceFilter.Compare, (GetType(iDOPropertyFilter).ToString())).Length > 0 Then
                                filters.Add(Activator.CreateInstance(filterAttr(x).Filter, New Object() {}))
                            Else
                                'code error.
                                Throw New InvalidCastException("Can not process a property filter which does not conform to the iDOPropertyFilter interface")
                            End If
                        End If
                    Next
                    element.Filters = filters.ToArray(GetType(iDOPropertyFilter))
                    ht.Add(element.PropertyName, element)
                End If
            End If
        Next
        h = ht
    End Sub

    'TODO: JVC Improve this description!
    ''' <summary>
    ''' cache the ancestry of this object and each ancestor internal reflective structure 
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks>
    ''' The last element in the ancestry is an introspection of this element. 
    ''' Each parent is one step backwards from there, therefore, the first element in the array is the ancestor furthest 
    ''' up the inheritance tree and the last element is the object itself. The base class DataObject will never be in the
    ''' ancestry array.
    ''' </remarks>
    Private Function CacheAncestry() As AncestryElement()
        Dim al As New ArrayList
        SyncLock Me
            Dim s As New Stack
            Dim baseType As Type = Me.GetType()
            While Not baseType.Equals(GetType(DataObject))
                s.Push(baseType)
                baseType = baseType.BaseType
            End While
            While s.Count > 0
                Dim t As Type = CType(s.Pop(), Type)
                Dim obj As DataObject
                If t.Equals(Me.GetType()) Then
                    obj = Me
                Else
                    obj = Activator.CreateInstance(t, New Object() {})
                End If
                Dim element As New AncestryElement
                element.Instance = obj
                CacheStructure(obj, element.StructureTable, element.PKName)
                al.Add(element)
            End While
        End SyncLock
        Return al.ToArray(GetType(AncestryElement))
    End Function
#End Region

#Region "Data Methods"
    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Commit the object (or its changes) to the database
    ''' </summary>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Sub Save()
        Me.SaveImpl(Nothing)
    End Sub

    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Commit the object (or its changes) to the database
    ''' </summary>
    ''' <param name="tObj">Optional: pass in the transaction object</param>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Sub Save(ByVal tObj As DOTransaction)
        SaveImpl(tObj)
    End Sub

    ''' <summary>
    ''' Private implementation of the Save method.
    ''' </summary>
    ''' <param name="tObj">a transaction context object to use for this save (nothing for no transactions)</param>
    ''' <remarks></remarks>
    Private Sub SaveImpl(ByVal tObj As DOTransaction)
        Dim localTransaction As Boolean
        If tObj Is Nothing Then
            localTransaction = True
            tObj = New DOTransaction(DOTransaction.IsolationLevel.ReadUncommitted)
        End If
        If m_touched = True Then
            For i As Integer = 0 To m_ancestry.Length - 1
                'implied logic: the last node in the array is the current object in the class heirarchy
                If i = m_ancestry.Length - 1 Then
                    If ExistsInTable() Then
                        Update(tObj, m_ancestry(i))
                    Else
                        Insert(tObj, m_ancestry(i))
                        m_commitUpdateContext = True
                    End If
                Else
                    If AncestorExistsInTable() Then
                        Update(tObj, m_ancestry(i))
                    Else
                        Insert(tObj, m_ancestry(i))
                        m_ancestorCommitUpdateContext = True
                    End If

                End If
            Next
        End If
        If localTransaction Then tObj.Commit()
        m_touched = False
    End Sub

    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Delete the object from the database.
    ''' </summary>
    ''' <param name="tObj">A DOTransaction context object to use for the delete (use null for no transaction)</param>
    ''' <remarks>
    ''' This item will be able to be saved again if any property is touched after deleting it. 
    ''' Requires appropriate database constraints to be fulfilled or a DOException will be thrown
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Sub Delete(ByVal tObj As DOTransaction)
        Delete(tObj, False)
    End Sub

    ''' <summary>
    ''' Delete the object from the database
    ''' </summary>
    ''' <param name="tObj">A DOTransaction context object to use for the delete (use null for no transaction)</param>
    ''' <param name="heirarchical">Delete all parent nodes in the class heirarchy as well</param>
    ''' <remarks></remarks>
    Public Sub Delete(ByVal tObj As DOTransaction, ByVal heirarchical As Boolean)
        Dim localTransaction As Boolean
        If tObj Is Nothing Then
            localTransaction = True
            tObj = New DOTransaction(DOTransaction.IsolationLevel.ReadUncommitted)
        End If

        Dim sqlStr As String
        Dim startIndex As Integer = Me.m_ancestry.Length - 1
        Dim endIndex As Integer = IIf(heirarchical, 0, startIndex)
        For i As Integer = startIndex To endIndex Step -1
            If i = startIndex Then
                m_commitUpdateContext = False
            Else
                m_ancestorCommitUpdateContext = False
            End If
            sqlStr = "delete from " & Me.m_ancestry(i).Instance.TableName() & " where " & Me.m_ancestry(i).PKName & "='" & Me.Uid.ToString() & "'"
            If tObj Is Nothing Then
                m_objDA.RunSQL(sqlStr)
            Else
                tObj.AddSQL(sqlStr)
            End If
        Next
        If localTransaction Then tObj.Commit()
        m_touched = False
    End Sub

    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Delete the object from the database. This will only delete the current object, not
    ''' its class-heirarchy related elements.
    ''' </summary>
    ''' <remarks>
    ''' This item will be able to be saved again if any property is touched after deleting it. 
    ''' Requires appropriate database constraints to be fulfilled or a DOException will be thrown
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Sub Delete()
        Delete(Nothing, False)
    End Sub

    ''' <summary>
    ''' Delete the object from the database. Setting the flag false will only delete the current 
    ''' object, not its class-heirarchy related elements. True will attempt to delete all parent 
    ''' elements.
    ''' </summary>
    ''' <param name="heirarchical">Delete all parent nodes in the class heirarchy as well</param>
    ''' <remarks>
    ''' Multiple inheritance from the same base element may cause a foreign key constraint violation
    ''' if the parent node is still used by another (differently) subclassed element. 
    ''' </remarks>
    Public Sub Delete(ByVal heirarchical As Boolean)
        Delete(Nothing, heirarchical)
    End Sub

    Public Sub Load()
        Me.Load(Nothing)
    End Sub

    'TODO: Add transactional support here...
    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Load the object from the database.
    ''' </summary>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Sub Load(ByVal tObj As DOTransaction)
        Dim sqlStr As String
        Dim tmpds As DataSet
        Dim currentElement As StructureElement
        Dim localTransaction As Boolean
        If tObj Is Nothing Then
            localTransaction = True
            tObj = New DOTransaction(DOTransaction.IsolationLevel.ReadUncommitted)
        End If

        For i As Integer = 0 To Me.m_ancestry.Length - 1
            sqlStr = "select * from " & Me.m_ancestry(i).Instance.TableName() & " where " & Me.m_ancestry(i).PKName & "='" & m_uid.ToString() & "'"
            tmpds = m_objDA.RunSQLReturnDataSet(sqlStr)

            If (tmpds.Tables.Count > 0) And (tmpds.Tables(0).Rows.Count > 0) Then
                Dim de As IDictionaryEnumerator = Me.m_ancestry(i).StructureTable.GetEnumerator()
                de.Reset()
                While de.MoveNext()
                    currentElement = CType(de.Value, StructureElement)

                    If tmpds.Tables(0).Rows(0).IsNull(currentElement.PropertyName) = True Then
                        Select Case currentElement.TypeName
                            Case TypeMapper.TYPE_DATE
                                Me.GetType().InvokeMember(currentElement.PropertyName, BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {Nothing})
                            Case TypeMapper.TYPE_STRING
                                Me.GetType().InvokeMember(currentElement.PropertyName, BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {""})
                            Case TypeMapper.TYPE_GUID
                                Me.GetType().InvokeMember(currentElement.PropertyName, BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {New Guid("00000000-0000-0000-0000-000000000000")})
                            Case Else
                                Me.GetType().InvokeMember(currentElement.PropertyName, BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {Nothing})
                        End Select
                    Else
                        Select Case currentElement.TypeName
                            Case TypeMapper.TYPE_GUID
                                Me.GetType().InvokeMember(currentElement.PropertyName, BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {CType(tmpds.Tables(0).Rows(0).Item(currentElement.PropertyName), System.Guid)})
                            Case TypeMapper.TYPE_BOOLEAN
                                Me.GetType().InvokeMember(currentElement.PropertyName, BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {CType(tmpds.Tables(0).Rows(0).Item(currentElement.PropertyName), Boolean)})
                            Case TypeMapper.TYPE_DOUBLE
                                Me.GetType().InvokeMember(currentElement.PropertyName, BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {CType(tmpds.Tables(0).Rows(0).Item(currentElement.PropertyName), Double)})
                            Case TypeMapper.TYPE_INTEGER, TypeMapper.TYPE_INTEGER32
                                Me.GetType().InvokeMember(currentElement.PropertyName, BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {CType(tmpds.Tables(0).Rows(0).Item(currentElement.PropertyName), Integer)})
                            Case TypeMapper.TYPE_DATE
                                If currentElement.TargetTypeName = SQLTypes.BizBloxDateType Then
                                    Me.GetType().InvokeMember(currentElement.PropertyName, BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {DateTime.ParseExact(CType(tmpds.Tables(0).Rows(0).Item(currentElement.PropertyName), String), "u", CultureInfo.InvariantCulture.DateTimeFormat)})
                                ElseIf currentElement.TargetTypeName = SQLTypes.SQLDateType Then
                                    Me.GetType().InvokeMember(currentElement.PropertyName, BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {CType(tmpds.Tables(0).Rows(0).Item(currentElement.PropertyName), DateTime)})
                                Else
                                    Throw New DOException("Unsupported DateTime to SQL Date Conversion")
                                End If
                            Case TypeMapper.TYPE_STRING
                                Dim tmp As String = CType(tmpds.Tables(0).Rows(0).Item(currentElement.PropertyName), String)
                                If currentElement.Filters.Length > 0 Then
                                    For x As Integer = 0 To currentElement.Filters.Length - 1
                                        tmp = currentElement.Filters(x).Input(tmp)
                                    Next
                                End If
                                Me.GetType().InvokeMember(currentElement.PropertyName, BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {tmp}) 'object ?
                            Case Else
                                Throw New DOException("Unsupported Type Conversion")
                        End Select
                    End If

                End While
                m_commitUpdateContext = True
            Else
                m_commitUpdateContext = False
                Throw New DOException("Failed load. DataObject does not exist in the database.")
            End If
        Next
        If localTransaction Then tObj.Commit()
        m_touched = False
    End Sub

    'TODO: JVC Update for inheritance
    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' copy all properties to a new object of the same type (except the UID)
    ''' </summary>
    ''' <param name="srcObj"></param>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Sub Copy(ByVal srcObj As Object)
        Dim myPropertyInfo() As PropertyInfo
        Dim tmpCol As String
        myPropertyInfo = Me.GetType().GetProperties()
        For i As Integer = 0 To myPropertyInfo.Length - 1
            tmpCol = myPropertyInfo(i).Name
            If Not tmpCol = UIDColumnName() Then
                Me.GetType().InvokeMember(tmpCol, BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {CType(Me.GetType().InvokeMember(tmpCol, BindingFlags.NonPublic Or BindingFlags.GetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, srcObj, New Object() {}), String)})
            End If
        Next i
    End Sub

    ''' <summary>
    ''' Read a property and prep it for a SQL commit.
    ''' </summary>
    ''' <param name="e">Maps a type using the StructureElement for transformations</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function ProcessProperty(ByVal e As StructureElement) As String
        Dim xObj As Object = Me.GetType().InvokeMember(e.PropertyName, BindingFlags.NonPublic Or BindingFlags.GetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {})
        If (e.CastType.Equals(GetType(String))) And (e.Filters.Length > 0) Then
            Dim tmp As String = xObj.ToString()
            For x As Integer = e.Filters.Length - 1 To 0 Step -1
                tmp = e.Filters(x).Output(tmp)
            Next
            xObj = tmp
        End If
        Return TypeMapper.TransformValue(xObj, e.TargetTypeName)
    End Function


    'TODO: Tidy comment
    'dynamic insert statement
    'this method iterates the properties of the object and
    'generates SQL to insert the data in to a table.
    'NOTE that data typing is not explicitly supported at present...
    'column names must mach property names.
    Private Sub Insert(ByVal tObj As DOTransaction, ByVal targetTypeElement As AncestryElement)
        Dim startedFlag As Boolean
        Dim currentElement As StructureElement
        Dim sqlStr As String
        Dim tmpVal As String

        Dim de As IDictionaryEnumerator = targetTypeElement.StructureTable.GetEnumerator()
        de.Reset()
        startedFlag = False
        sqlStr = "insert into " & targetTypeElement.Instance.TableName() & "("
        While de.MoveNext()
            If startedFlag = False Then
                startedFlag = True
            Else
                sqlStr = sqlStr & ","
            End If
            sqlStr = sqlStr & CType(de.Value, StructureElement).PropertyName
        End While

        de.Reset()
        startedFlag = False
        sqlStr = sqlStr & ") values ("
        While de.MoveNext()
            currentElement = CType(de.Value, StructureElement)
            If startedFlag = False Then
                startedFlag = True
            Else
                sqlStr = sqlStr & ","
            End If
            tmpVal = ProcessProperty(currentElement) '.PropertyName, currentElement.TargetTypeName)

            If tmpVal Is Nothing And (Not currentElement.TargetTypeName = SQLTypes.StringType) Then
                sqlStr = sqlStr & " NULL "
            ElseIf (tmpVal Is Nothing Or tmpVal = "") And currentElement.TargetTypeName = SQLTypes.StringType Then
                sqlStr = sqlStr & "''"
            Else
                sqlStr = sqlStr & "'" & Replace(tmpVal, "'", "''") & "'"
            End If
        End While
        sqlStr = sqlStr & ")"
        If tObj Is Nothing Then
            m_objDA.RunSQL(sqlStr)
        Else
            tObj.AddSQL(sqlStr)
        End If
    End Sub

    'TODO: Tidy comment
    'dynamic update statement
    'this method iterates the properties of the object and
    'generates SQL to update the data in to a table.
    'NOTE that data typing is not explicitly supported at present...
    'column names must mach property names.
    Private Sub Update(ByVal tObj As DOTransaction, ByVal targetTypeElement As AncestryElement)
        Dim addComma As Boolean = False
        Dim startedFlag As Boolean
        Dim currentElement As StructureElement

        Dim sqlStr As String
        Dim tmpVal As String

        Dim de As IDictionaryEnumerator = targetTypeElement.StructureTable.GetEnumerator()
        de.Reset()
        startedFlag = False
        sqlStr = "update " & targetTypeElement.Instance.TableName() & " set "
        While de.MoveNext()
            currentElement = CType(de.Value, StructureElement)
            If startedFlag = False Then
                startedFlag = True
            ElseIf Not currentElement.PKColumn Then
                'don't add the extra comma on a PK column
                If addComma Then
                    sqlStr = sqlStr & ","
                Else
                    addComma = True
                End If

                sqlStr = sqlStr & currentElement.PropertyName & " = "
                tmpVal = ProcessProperty(currentElement) '.PropertyName, currentElement.TargetTypeName)

                If tmpVal Is Nothing And (Not currentElement.TargetTypeName = SQLTypes.StringType) Then
                    sqlStr = sqlStr & " NULL "
                ElseIf (tmpVal Is Nothing Or tmpVal = "") And currentElement.TargetTypeName = SQLTypes.StringType Then
                    sqlStr = sqlStr & "''"
                Else
                    sqlStr = sqlStr & "'" & Replace(tmpVal, "'", "''") & "'"
                End If
            End If

        End While

        'key the update against this UID.
        sqlStr = sqlStr & " where " & targetTypeElement.PKName & "='" & m_uid.ToString() & "'"
        If tObj Is Nothing Then
            m_objDA.RunSQL(sqlStr)
        Else
            tObj.AddSQL(sqlStr)
        End If
    End Sub
#End Region

#Region "Special Properties Methods"
    'get this object UID
    Public Function Uid() As Guid
        Return m_uid
    End Function

    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Does this object exist in the table it maps to?
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Function ExistsInTable() As Boolean
        Return m_commitUpdateContext
    End Function

    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Does this objects ancestor exist in the table it maps to?
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	30/3/2007	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Function AncestorExistsInTable() As Boolean
        Return m_ancestorCommitUpdateContext
    End Function

    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Determine if a value is EQUIVALENT to a null.
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Shared Function IsNull(ByVal dtVal As DateTime) As Boolean
        If dtVal = Nothing Then Return True
        Dim dtStr As String = dtVal.ToString("yyyy-MM-dd HH:mm:ssZ")
        If dtStr = "0001-01-01 00:00:00Z" Then Return True
        Return False
    End Function

    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Determine if a value is EQUIVALENT to a null.
    ''' </summary>
    ''' <param name="gVal"></param>
    ''' <returns></returns>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Shared Function IsNull(ByVal gVal As Guid) As Boolean
        If gVal.ToString() = "00000000-0000-0000-0000-000000000000" Then Return True
        Return False
    End Function

    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Get the column name of the Primary Key (UniqueID)
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Function UIDColumnName() As String
        Return Me.m_ancestry(Me.m_ancestry.Length - 1).PKName
    End Function

    Public Function UIDColumn() As [Enum]
        Return [Enum].Parse(Type.GetType(Me.TableName & ".Columns"), Me.m_ancestry(Me.m_ancestry.Length - 1).PKName)
    End Function

    'TODO: JVC Update for inheritance
    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Get a data transfer object of the type defined by [toType] and populate it from
    ''' the data contained in the DataObject
    ''' </summary>
    ''' <param name="toType"></param>
    ''' <returns></returns>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	19/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Function GetTO(ByVal toType As Type) As DOTO
        Dim myPropertyInfo() As PropertyInfo = Me.GetType().GetProperties()
        Dim toObj As DOTO
        toObj = Activator.CreateInstance(toType)
        For i As Integer = 0 To myPropertyInfo.Length - 1
            If Not toType.GetProperty(myPropertyInfo(i).Name) Is Nothing Then
                toType.InvokeMember(myPropertyInfo(i).Name, BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, toObj, New Object() {Me.GetType().InvokeMember(myPropertyInfo(i).Name, BindingFlags.NonPublic Or BindingFlags.GetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {})})
            End If
        Next i
        Return toObj
    End Function


    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Accept a transfer object (which inherits from DOTO) to populate this DataObject
    ''' 
    ''' </summary>
    ''' <param name="toObj"></param>
    ''' <remarks>
    ''' This method will NOT update the primary key for this data object even if the transfer object has a 
    ''' DIFFERENT value for the PK
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	19/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Public Sub AcceptTO(ByVal toObj As DOTO)
        Dim myPropertyInfo() As PropertyInfo = Me.GetType().GetProperties()
        For i As Integer = 0 To myPropertyInfo.Length - 1
            If Not toObj.GetType().GetProperty(myPropertyInfo(i).Name) Is Nothing Then
                Try
                    If Not myPropertyInfo(i).Name = Me.UIDColumnName() Then
                        Me.GetType().InvokeMember(myPropertyInfo(i).Name, BindingFlags.NonPublic Or BindingFlags.SetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, Me, New Object() {toObj.GetType().InvokeMember(myPropertyInfo(i).Name, BindingFlags.NonPublic Or BindingFlags.GetProperty Or BindingFlags.Public Or BindingFlags.Instance, Nothing, toObj, New Object() {})})
                    End If
                Catch ex As Exception
                    Throw New DOException("Could not accept transfer object", ex)
                End Try
            End If
        Next i
    End Sub
#End Region

#Region "SubClass Utility Method"
    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' all subclassed property SET methods *must* call this 
    ''' subroutine to flag that the object has changed and 
    ''' needs to be committed to the database.
    ''' </summary>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	26/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    Protected Sub Touch()
        m_touched = True
    End Sub

    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Check if a string extends beyond a predefined length - throw exception if it does
    ''' </summary>
    ''' <param name="param"></param>
    ''' <param name="input"></param>
    ''' <param name="limit"></param>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	19/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    ''' 
    Protected Sub BoundsCheck(ByVal param As [Enum], ByVal input As String, ByVal limit As Integer)
        If input Is Nothing Then Return
        If input.Length > limit Then Throw New ArgumentOutOfRangeException(param.ToString(), input, "This field is limited to " & limit & " characters.")
    End Sub

    ''' -----------------------------------------------------------------------------
    ''' <summary>
    ''' Check if a date extends beyond a predefined length when stored as string - throw exception if it does
    ''' </summary>
    ''' <param name="param"></param>
    ''' <param name="input"></param>
    ''' <param name="limit"></param>
    ''' <remarks>
    ''' </remarks>
    ''' <history>
    ''' 	[cinj]	19/08/2004	Created
    ''' </history>
    ''' -----------------------------------------------------------------------------
    ''' 
    Protected Sub BoundsCheck(ByVal param As [Enum], ByVal input As DateTime, ByVal limit As Integer)
        Dim tmp As String = input.ToString("yyyy-MM-dd HH:mm:ssZ")
        If tmp.Length > limit Then Throw New ArgumentOutOfRangeException(param.ToString(), input, "This field is limited to " & limit & " characters.")
    End Sub

#End Region

End Class

